package cn.lenchu.stock.xueqiu;

import cn.hutool.core.collection.CollectionUtil;
import cn.lenchu.stock.model.Stock;
import cn.lenchu.stock.model.StockCodeConverter;
import cn.lenchu.stock.service.StockService;
import cn.lenchu.util.DateTimeUtil;
import cn.lenchu.util.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;

import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lenchu
 */
@Service
public class StockServiceXueQiuImpl implements StockService {

    private final HttpUtil http = HttpUtil.newInstance();
    private final StockCodeConverter<XueQiuCode> codeConverter = new XueQiuCodeConverter();

    private void refreshToken() {
        ResponseEntity<String> responseEntity = http.get("https://xueqiu.com/");
        HttpHeaders headers = responseEntity.getHeaders();
        List<String> cookies = headers.get("Set-Cookie");
        String cookie = null;
        if (cookies != null) {
            for (String s : cookies) {
                if (StringUtils.indexOf(s, "xq_a_token") != -1) {
                    cookie = s;
                    break;
                }
            }
        }
        if (cookie != null) {
            String token = cookie.split(";")[0];
            http.removeDefaultHeader("Cookie");
            http.addDefaultHeader("Cookie", token);
        }
    }

    @Override
    public boolean supportCode(String code) {
        XueQiuCode xueQiuCode = codeConverter.convert(code);
        return xueQiuCode != null;
    }

    @Override
    public List<Stock> searchStock(String keyword) {
        try {
            String encodedKeyword = URLEncoder.encode(keyword, "UTF-8");
            ResponseEntity<String> responseEntity = http.get("https://xueqiu.com/stock/search.json?code=" + encodedKeyword);
            int statusCodeValue = responseEntity.getStatusCodeValue();
            if (statusCodeValue == HttpStatus.OK.value()) {
                JSONObject responseBody = JSONObject.parseObject(responseEntity.getBody());
                JSONArray stockArray = responseBody.getJSONArray("stocks");
                List<StockSearchResult> searchResultList = stockArray.toJavaList(StockSearchResult.class);
                List<Stock> result = new ArrayList<>(searchResultList.size());
                for (StockSearchResult searchResult : searchResultList) {
                    Stock stock = extractStock(searchResult);
                    if (stock != null) {
                        result.add(stock);
                    }
                }
                return result;
            }
        } catch (HttpClientErrorException httpClientErrorException) {
            int rawStatusCode = httpClientErrorException.getRawStatusCode();
            if (rawStatusCode == HttpStatus.BAD_REQUEST.value()) {
                refreshToken();
                return searchStock(keyword);
            }
        } catch (Exception ignored) {}
        return null;
    }

    private Stock extractStock(StockSearchResult searchResult) {
        String xqCode = searchResult.getCode();
        Integer type = searchResult.getType();
        XueQiuCode xueQiuCode = XueQiuCode.fromXqCodeAndType(xqCode, type);
        if (xueQiuCode == null) {
            return null;
        }

        XqStock xqStock = new XqStock();
        xqStock.setName(searchResult.getName());
        xqStock.setCode(xueQiuCode.getStandardCode());
        return xqStock;
    }

    private XqStock extractStock(ResponseEntity<String> responseEntity, String standardCode) {
        XqStock result = new XqStock();
        JSONObject quote = Optional.ofNullable(responseEntity)
            .map(ResponseEntity::getBody)
            .map(JSONObject::parseObject)
            .map(jo -> jo.getJSONObject("data"))
            .map(data -> data.getJSONObject("quote"))
            .orElse(null);
        if (quote == null) {
            return result;
        }
        result.setCode(standardCode);
        fillStock(quote, result);
        return result;
    }

    private void fillStock(JSONObject quote, XqStock xqStock) {
        Double current = quote.getDouble("current");
        String name = quote.getString("name");
        Long time = quote.getLong("time");
        xqStock.setName(name);
        xqStock.setPrice(current);
        xqStock.setUpdateTime(time);
    }

    private void fillJj(ResponseEntity<String> responseEntity, XqStock result) {
        Optional.ofNullable(responseEntity)
            .map(ResponseEntity::getBody)
            .map(JSONObject::parseObject)
            .map(jo -> jo.getJSONObject("data"))
            .map(data -> data.getJSONArray("fund_nav_growth"))
            .map(array -> array.get(array.size() - 1))
            .map(obj -> {
                if (obj instanceof JSONObject) {
                    return (JSONObject) obj;
                }
                return null;
            })
            .ifPresent(jo -> {
                Double nav = jo.getDouble("nav");
                result.setPrice(nav);
                String date = jo.getString("date");
                if (StringUtils.isNotBlank(date)) {
                    LocalDateTime dateTime = LocalDateTime.parse(date + " 23:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
                    result.setUpdateTime(DateTimeUtil.dateTimeToTimestamp(dateTime));
                }
            });
    }

    @Override
    public Stock getByCode(String code) {
        try {
            XueQiuCode xqCode = codeConverter.convert(code);
            if (xqCode == null) {
                return null;
            }
            ResponseEntity<String> responseEntity = http.get("https://stock.xueqiu.com/v5/stock/quote.json?symbol=" + xqCode.getXqCode());
            XqStock result = extractStock(responseEntity, xqCode.getStandardCode());
            if (xqCode.isJj()) {
                responseEntity = http.get("https://fund.xueqiu.com/dj/open/fund/growth/" + xqCode.getCode() + "?day=30");
                fillJj(responseEntity, result);
            }
            return result;
        } catch (HttpClientErrorException httpClientErrorException) {
            int rawStatusCode = httpClientErrorException.getRawStatusCode();
            if (rawStatusCode == HttpStatus.BAD_REQUEST.value()) {
                refreshToken();
                return getByCode(code);
            }
        } catch (Exception ignored) {}
        return null;
    }

    @Override
    public Map<String, Stock> batchGetByCode(List<String> codeList) {
        Map<String, Stock> result = new HashMap<>(codeList.size());
        Map<String, XueQiuCode> codeMap = codeList.stream()
            .map(codeConverter::convert)
            .collect(Collectors.toMap(XueQiuCode::getXqCode, xueQiuCode -> xueQiuCode, (e1, e2) -> e2));
        try {
            String symbols = CollectionUtil.join(codeMap.keySet(), ",");
            ResponseEntity<String> responseEntity = http.get("https://stock.xueqiu.com/v5/stock/batch/quote.json?symbol=" + symbols);
            JSONArray items = Optional.ofNullable(responseEntity)
                .map(ResponseEntity::getBody)
                .map(JSONObject::parseObject)
                .map(jo -> jo.getJSONObject("data"))
                .map(data -> data.getJSONArray("items"))
                .orElse(null);
            if (items == null) {
                return result;
            }
            for (Object item : items) {
                if (item instanceof JSONObject) {
                    JSONObject quote = ((JSONObject) item).getJSONObject("quote");
                    String symbol = quote.getString("symbol");
                    XueQiuCode xueQiuCode = codeMap.get(symbol);
                    if (xueQiuCode != null) {
                        XqStock xqStock = new XqStock();
                        String standardCode = xueQiuCode.getStandardCode();
                        xqStock.setCode(standardCode);
                        fillStock(quote, xqStock);
                        if (xueQiuCode.isJj()) {
                            responseEntity = http.get("https://fund.xueqiu.com/dj/open/fund/growth/" + xueQiuCode.getCode() + "?day=30");
                            fillJj(responseEntity, xqStock);
                        }
                        result.put(standardCode, xqStock);
                    }
                }
            }
        } catch (HttpClientErrorException httpClientErrorException) {
            int rawStatusCode = httpClientErrorException.getRawStatusCode();
            if (rawStatusCode == HttpStatus.BAD_REQUEST.value()) {
                refreshToken();
                return batchGetByCode(codeList);
            }
        } catch (Exception ignored) {}
        return result;
    }
}
